package com.dynamicthreadpool.spi;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * URL类
 * 从Dubbo 2.7版本提取的完整实现
 */
public class URL implements Serializable {
    private static final long serialVersionUID = -1855423948236684084L;

    private final String protocol;
    private final String username;
    private final String password;
    private final String host;
    private final int port;
    private final String path;
    private final Map<String, String> parameters;

    public URL() {
        this(null, null, null, null, 0, null, null);
    }

    public URL(String protocol, String host, int port) {
        this(protocol, null, null, host, port, null, (Map<String, String>) null);
    }

    public URL(String protocol, String host, int port, String path) {
        this(protocol, null, null, host, port, path, (Map<String, String>) null);
    }

    public URL(String protocol, String host, int port, Map<String, String> parameters) {
        this(protocol, null, null, host, port, null, parameters);
    }

    public URL(String protocol, String host, int port, String path, Map<String, String> parameters) {
        this(protocol, null, null, host, port, path, parameters);
    }

    public URL(String protocol, String username, String password, String host, int port, String path, Map<String, String> parameters) {
        this.protocol = protocol;
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = port < 0 ? 0 : port;
        this.path = path;
        if (parameters == null) {
            this.parameters = new ConcurrentHashMap<>();
        } else {
            this.parameters = new ConcurrentHashMap<>(parameters);
        }
    }

    public String getProtocol() {
        return protocol;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getHost() {
        return host;
    }

    public int getPort() {
        return port;
    }

    public String getPath() {
        return path;
    }

    public String getParameter(String key) {
        return getParameter(key, (String) null);
    }

    public String getParameter(String key, String defaultValue) {
        String value = parameters.get(key);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        return value;
    }

    public Integer getParameter(String key, Integer defaultValue) {
        String value = getParameter(key);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    public Boolean getParameter(String key, Boolean defaultValue) {
        String value = getParameter(key);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        return Boolean.parseBoolean(value);
    }

    public Double getParameter(String key, Double defaultValue) {
        String value = getParameter(key);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        try {
            return Double.parseDouble(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    public Long getParameter(String key, Long defaultValue) {
        String value = getParameter(key);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    public URL setParameter(String key, String value) {
        if (value == null) {
            removeParameter(key);
            return this;
        }
        Map<String, String> params = new HashMap<>(parameters);
        params.put(key, value);
        return new URL(protocol, username, password, host, port, path, params);
    }

    public URL removeParameter(String key) {
        if (!parameters.containsKey(key)) {
            return this;
        }
        Map<String, String> params = new HashMap<>(parameters);
        params.remove(key);
        return new URL(protocol, username, password, host, port, path, params);
    }

    public URL addParameters(Map<String, String> parameters) {
        Map<String, String> params = new HashMap<>(this.parameters);
        if (parameters != null) {
            params.putAll(parameters);
        }
        return new URL(protocol, username, password, host, port, path, params);
    }

    public Map<String, String> getParameters() {
        return new HashMap<>(parameters);
    }

    public boolean hasParameter(String key) {
        String value = getParameter(key);
        return value != null && value.length() > 0;
    }

    public Set<String> getParameterKeys() {
        return new HashSet<>(parameters.keySet());
    }

    public URL setPath(String path) {
        return new URL(protocol, username, password, host, port, path, parameters);
    }

    @Override
    public String toString() {
        return buildString();
    }

    private String buildString() {
        StringBuilder sb = new StringBuilder();
        if (protocol != null && protocol.length() > 0) {
            sb.append(protocol);
            sb.append("://");
        }
        if (username != null && username.length() > 0) {
            sb.append(username);
            if (password != null && password.length() > 0) {
                sb.append(":");
                sb.append(password);
            }
            sb.append("@");
        }
        if (host != null && host.length() > 0) {
            sb.append(host);
            if (port > 0) {
                sb.append(":");
                sb.append(port);
            }
        }
        if (path != null && path.length() > 0) {
            if (path.charAt(0) != '/') {
                sb.append('/');
            }
            sb.append(path);
        }
        if (parameters != null && parameters.size() > 0) {
            sb.append('?');
            boolean first = true;
            for (Map.Entry<String, String> entry : parameters.entrySet()) {
                if (first) {
                    first = false;
                } else {
                    sb.append('&');
                }
                sb.append(entry.getKey());
                sb.append('=');
                sb.append(entry.getValue() == null ? "" : entry.getValue());
            }
        }
        return sb.toString();
    }

    @Override
    public int hashCode() {
        int result = protocol != null ? protocol.hashCode() : 0;
        result = 31 * result + (username != null ? username.hashCode() : 0);
        result = 31 * result + (password != null ? password.hashCode() : 0);
        result = 31 * result + (host != null ? host.hashCode() : 0);
        result = 31 * result + port;
        result = 31 * result + (path != null ? path.hashCode() : 0);
        result = 31 * result + parameters.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        URL url = (URL) o;

        if (port != url.port) return false;
        if (protocol != null ? !protocol.equals(url.protocol) : url.protocol != null) return false;
        if (username != null ? !username.equals(url.username) : url.username != null) return false;
        if (password != null ? !password.equals(url.password) : url.password != null) return false;
        if (host != null ? !host.equals(url.host) : url.host != null) return false;
        if (path != null ? !path.equals(url.path) : url.path != null) return false;
        return parameters.equals(url.parameters);
    }

    public static URL valueOf(String url) {
        if (url == null || url.length() == 0) {
            throw new IllegalArgumentException("url == null");
        }
        String protocol = null;
        String username = null;
        String password = null;
        String host = null;
        int port = 0;
        String path = null;
        Map<String, String> parameters = null;

        int i = url.indexOf(':');
        if (i >= 0) {
            protocol = url.substring(0, i);
            url = url.substring(i + 1);
        }

        if (url.startsWith("//")) {
            url = url.substring(2);
            i = url.indexOf('/');
            if (i >= 0) {
                String authority = url.substring(0, i);
                path = url.substring(i + 1);
                url = authority;
            }
            i = url.indexOf('@');
            if (i >= 0) {
                String userInfo = url.substring(0, i);
                url = url.substring(i + 1);
                i = userInfo.indexOf(':');
                if (i >= 0) {
                    username = userInfo.substring(0, i);
                    password = userInfo.substring(i + 1);
                } else {
                    username = userInfo;
                }
            }
            i = url.indexOf(':');
            if (i >= 0) {
                host = url.substring(0, i);
                url = url.substring(i + 1);
                i = url.indexOf('/');
                if (i >= 0) {
                    port = Integer.parseInt(url.substring(0, i));
                    path = url.substring(i + 1);
                } else {
                    port = Integer.parseInt(url);
                }
            } else {
                host = url;
            }
        } else {
            i = url.indexOf('/');
            if (i >= 0) {
                path = url.substring(i + 1);
                url = url.substring(0, i);
            }
            i = url.indexOf(':');
            if (i >= 0) {
                host = url.substring(0, i);
                port = Integer.parseInt(url.substring(i + 1));
            } else {
                host = url;
            }
        }

        i = path != null ? path.indexOf('?') : -1;
        if (i >= 0) {
            String query = path.substring(i + 1);
            path = path.substring(0, i);
            parameters = parseQueryString(query);
        }

        return new URL(protocol, username, password, host, port, path, parameters);
    }

    private static Map<String, String> parseQueryString(String query) {
        if (query == null || query.length() == 0) {
            return new HashMap<>();
        }
        Map<String, String> result = new HashMap<>();
        String[] pairs = query.split("&");
        for (String pair : pairs) {
            int i = pair.indexOf('=');
            if (i >= 0) {
                result.put(pair.substring(0, i), pair.substring(i + 1));
            } else {
                result.put(pair, "");
            }
        }
        return result;
    }
}
